#region New BSD License

// Copyright (c) 2012, John Batte
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification, are permitted
// provided that the following conditions are met:
// 
// Redistributions of source code must retain the above copyright notice, this list of conditions
// and the following disclaimer.
// 
// Redistributions in binary form must reproduce the above copyright notice, this list of conditions
// and the following disclaimer in the documentation and/or other materials provided with the distribution.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

using FluentAssertions;

using Moq;

using Patterns.Specifications.Common;
using Patterns.Specifications.Properties;
using Patterns.Specifications.Subjects;

using TechTalk.SpecFlow;

namespace Patterns.Specifications.Steps
{
	[Binding]
	public class TestAssertions
	{
		[Then(@"the method call should abort")]
		public void CheckEmptyCallResult()
		{
			TestAutomation.CallResult.Should().BeNull(Resources.CheckEmptyCallResultReason);
		}

		[Then(@"the method call should complete with no errors")]
		public void CheckValidCallResult()
		{
			TestAutomation.CallResult.Should().Be(TestAutomation.Finish, Resources.CheckValidCallResultReason);
		}

		[Then(@"the feed for method call requests should have returned (.+) item")]
		public void CheckCallRequestCount(int count)
		{
			int actual = TestObservations.CallRequests.Count;
			int expected = TestObservations.LastCapturedReadRequestCount + count;
			actual.Should().Be(expected, Resources.CheckCallRequestCountReasonFormat, count);
		}

		[Then(@"the feed for method call responses should have returned (.+) item(.+)?")]
		public void CheckCallResponseCount(int count, string trailingS)
		{
			int actual = TestObservations.CallResponses.Count;
			int expected = TestObservations.LastCapturedCallResponseCount + count;
			actual.Should().Be(expected, Resources.CheckCallResponseCountReasonFormat, count);
		}

		[Then(@"the feed for property read requests should have returned (.+) item")]
		public void CheckReadRequestCount(int count)
		{
			int actual = TestObservations.ReadRequests.Count;
			int expected = TestObservations.LastCapturedReadRequestCount + count;
			actual.Should().Be(expected, Resources.CheckReadRequestCountReasonFormat, count);
		}

		[Then(@"the feed for property read responses should have returned (.+) item(.+)?")]
		public void CheckReadResponseCount(int count, string trailingS)
		{
			int actual = TestObservations.ReadResponses.Count;
			int expected = TestObservations.LastCapturedReadResponseCount + count;
			actual.Should().Be(expected, Resources.CheckReadResponseCountReasonFormat, count);
		}

		[Then(@"the feed for property write requests should have returned (.+) item(.+)?")]
		public void CheckWriteRequestCount(int count, string trailingS)
		{
			int actual = TestObservations.WriteRequests.Count;
			int expected = TestObservations.LastCapturedWriteRequestCount + count;
			actual.Should().Be(expected, Resources.CheckWriteRequestCountReasonFormat, count);
		}

		[Then(@"the feed for errors should have returned (.+) item(s)?")]
		public void CheckErrorCount(int count, string trailingS)
		{
			int actual = TestObservations.Errors.Count;
			int expected = TestObservations.LastCapturedErrorCount + count;
			actual.Should().Be(expected, Resources.CheckErrorCountReasonFormat, count);
		}

		[Then(@"the value I read should be the default value for the return type")]
		public void CheckPropertyReadResultIsDefault()
		{
			object defaultPropertyValue = TemperamentalTestSubject.TestPropertyType.GetDefault();
			TestAutomation.CallResult.Should().Be(defaultPropertyValue, Resources.CheckPropertyReadResultIsDefaultReason);
		}

		[Then(@"the value that I read should be the value I expected")]
		public void CheckPropertyReadResult()
		{
			object expectedPropertyValue = TemperamentalTestSubject.DefaultPropertyValue;
			TestAutomation.CallResult.Should().Be(expectedPropertyValue, Resources.CheckPropertyReadResultReason);
		}

		[Then(@"the value that I read should be the value I wrote")]
		public void CheckPropertyWriteResult()
		{
			object expectedPropertyValue = TestAutomation.PropertyWriteValue;
			TestAutomation.CallResult.Should().Be(expectedPropertyValue, Resources.CheckPropertyWriteResultReason);
		}

		[Then(@"an ArgumentNullException for the (.+) argument should have been thrown")]
		public void CheckArgumentNullException(string paramName)
		{
			TestAutomation.CallResult.Should().BeOfType<ArgumentNullException>(Resources.ExpectedExceptionFromLastStepReason);
			var exception = (ArgumentNullException) TestAutomation.CallResult;
			exception.ParamName.Should().Be(paramName, Resources.LastStepErrorArgumentFormat, paramName);
		}

		[Then(@"the collection should contain (.+) item(s)?")]
		public void CheckCollectionCount(int count, string trailingS)
		{
			TestFactory.SubjectCollection.Should().HaveCount(count);
		}

		[Then(@"the collection should be null")]
		public void CheckCollectionIsNull()
		{
			TestFactory.SubjectCollection.Should().BeNull(Resources.CheckCollectionIsNullReason);
		}

		[Then(@"the mocked test bucket's Add method should have been called (.+) time(s)?")]
		public void VerifyTestBucketAdd(int count, string trailingS)
		{
			TestFactory.Mocks.GetMock<ITestBucket>().Verify(bucket => bucket.Add(It.IsAny<TestSubject>()), Times.Exactly(count));
		}

		[Then(@"each item in the test bucket should have a thread Id")]
		public void VerifyTestBucketContentsThreadId()
		{
			TestFactory.SubjectCollection.Should().OnlyContain(subject => subject.ThreadId.HasValue);
		}

		[Then(@"there should be more than one distinct thread Id in the test bucket's items")]
		public void VerifyTestBucketContentsMultipleThreadIds()
		{
			IEnumerable<int?> distinctIds = TestFactory.SubjectCollection.Select(subject => subject.ThreadId).Distinct();
			distinctIds.Should().HaveCount(count => count > 1);
		}

		[Then(@"the DateTime values should be equal")]
		[Then(@"the results of both ""now"" DateTime values should be equal")]
		public void VerifyBothNowValuesMatch()
		{
			var custom = TestObservations.PrimaryDateTime;
			var customValue = new DateTime(custom.Year, custom.Month, custom.Day, custom.Hour, custom.Minute, custom.Second);
			var system = TestObservations.SecondaryDateTime;
			var systemValue = new DateTime(system.Year, system.Month, system.Day, system.Hour, system.Minute, system.Second);
			customValue.Should().Be(systemValue);
		}

		[Given(@"the DateTime values vary by millisecond")]
		public void VerifyBothNowValuesVaryByMillisecond()
		{
			var primary = TestObservations.PrimaryDateTime;
			var secondary = TestObservations.SecondaryDateTime;
			primary.Should().NotBe(secondary);
			primary.Year.Should().Be(secondary.Year);
			primary.Month.Should().Be(secondary.Month);
			primary.Day.Should().Be(secondary.Day);
			primary.Hour.Should().Be(secondary.Hour);
			primary.Minute.Should().Be(secondary.Minute);
			primary.Second.Should().Be(secondary.Second);
			primary.Millisecond.Should().NotBe(secondary.Millisecond);
		}

		[Then(@"the options should include the compiled option")]
		public void VerifyRegularExpressionIsCompiled()
		{
			RegexOptions compiledOption = TestObservations.RegularExpressionOptions & RegexOptions.Compiled;
			compiledOption.Should().Be(RegexOptions.Compiled);
		}

		[Then(@"each string in the set should have resulted in a positive match against the pattern")]
		public void VerifyAllInputStringsMatchedRegex()
		{
			TestObservations.RegularExpressionMatches.Should().HaveSameCount(TestFactory.RegularExpressionInputStrings);
			TestObservations.RegularExpressionMatches.Should().OnlyContain(match => match.Success);
		}

		[Then(@"all expected group values \((.+)\) should be found in the resulting dictionary")]
		public void VerifyExpectedGroupValues(string groupValues)
		{
			IDictionary<string, string> expectedValues = TextParser.ParseSimpleDictionaryString(groupValues);
			TestObservations.RegularExpressionMatchDictionary.Should().HaveCount(expectedValues.Count);
			string[] keys = expectedValues.Keys.ToArray();
			string[] values = expectedValues.Values.ToArray();
			TestObservations.RegularExpressionMatchDictionary.Should().ContainKeys(keys).And.ContainValues(values);
		}

		[Then(@"the pattern string I read from the CompiledRegex should match the pattern string used to create it")]
		public void VerifyRegexPatternMatchesOriginalPattern()
		{
			TestObservations.RegularExpressionExtractedPattern.Should().Be(TestFactory.RegularExpressionPatternString);
		}
	}
}